mod iwlib_sys;
use iwlib_sys::*;
use std::{
    ffi::{c_char, c_int, CStr, CString},
    vec,
};
#[macro_use]
mod err;
pub use err::*;
use log::debug;

#[derive(Debug, Clone, Copy)]
#[repr(u32)]
pub enum Mode {
    Auto = 0,
    AdHoc = 1,
    Managed = 2,
    Master = 3,
    Repeater = 4,
    Secondary = 5,
    Monitor = 6,
    Unknown = 7,
}

#[derive(Debug, Clone)]
pub struct WifiInfo {
    pub ssid: String,
    pub freq: f64,
    pub rssi: i32,
}

pub struct IW {
    skfd: i32,
    we_version: i32,
}

unsafe impl Send for IW {}

#[derive(Default, Clone)]
#[repr(transparent)]
pub struct IWRange(iw_range);

impl IW {
    pub fn new() -> Result<Self> {
        let skfd = unsafe { iw_sockets_open() };

        if skfd < 0 {
            return Err(IWError::Socket);
        }

        let we_version = unsafe { iw_get_kernel_we_version() };

        Ok(Self { skfd, we_version })
    }

    pub fn print_version_info(&self) -> Result {
        let toolname = c"iw";
        unsafe {
            iw_bail!(iw_print_version_info(toolname.as_ptr()));
        }
        Ok(())
    }

    pub fn enum_devices(&self, is_vaild: bool) -> Vec<CString> {
        let mut list = unsafe {
            let list = Box::new(Vec::<CString>::new());
            let list_ptr = Box::into_raw(list);
            let mut list_ptr2 = list_ptr as *mut c_char;
            unsafe extern "C" fn handler(
                _: c_int,
                ifname: *mut c_char,
                args: *mut *mut c_char,
                _: c_int,
            ) -> c_int {
                let name_bytes = CStr::from_ptr(ifname).to_bytes_with_nul().to_vec();
                let name = CString::from_vec_with_nul(name_bytes).unwrap();
                let list_ptr = args as *mut *mut Vec<CString>;
                let ptr = *list_ptr;
                let list = &mut *ptr;
                list.push(name);
                0
            }

            iw_enum_devices(self.skfd, Some(handler), (&mut list_ptr2) as _, 0);
            let list = Box::from_raw(list_ptr);
            list.to_vec()
        };
        if is_vaild {
            list = list
                .into_iter()
                .filter(|one| {
                    let mut info = wireless_info::default();
                    unsafe { iw_get_basic_config(self.skfd, one.as_ptr(), &mut info.b) >= 0 }
                })
                .collect();
        }
        list
    }

    unsafe fn set_ext(&self, ifname: &CString, request: u32, pwrq: &mut iwreq) -> Result {
        /* Set device name */
        strncpy(
            pwrq.ifr_ifrn.ifrn_name.as_mut_ptr(),
            ifname.as_ptr(),
            IFNAMSIZ as _,
        );
        /* Do the request */
        iw_bail!(ioctl(self.skfd, request as _, pwrq));

        Ok(())
    }
    // unsafe fn get_ext(&self, ifname: &str, request: u32)->Result<iwreq>{
    //     let ifname = CString::new(ifname).unwrap();
    //     let mut pwrq = iwreq::default();
    //     /* Set device name */
    //     strncpy(pwrq.ifr_ifrn.ifrn_name.as_mut_ptr(), ifname.as_ptr(), IFNAMSIZ as _);
    //     /* Do the request */
    //     iw_bail!(ioctl(self.skfd, request as _ , &mut pwrq));
    //     Ok(pwrq)
    // }
    pub fn set_mode(&self, ifname: &CString, mode: Mode) -> Result {
        let mut wrq = iwreq::default();
        unsafe {
            wrq.u.mode = mode as _;
            self.set_ext(ifname, SIOCSIWMODE, &mut wrq)
        }
    }

    pub fn set_freq(&self, ifname: &CString, freq: f64) -> Result {
        let mut wrq = iwreq::default();
        unsafe {
            iw_float2freq(freq, &mut wrq.u.freq);
            wrq.u.freq.flags = IW_FREQ_FIXED as _;
            self.set_ext(ifname, SIOCSIWFREQ, &mut wrq)
        }
    }

    pub fn scan(&self, ifname: &CString) -> Result<Vec<WifiInfo>> {
        let mut out = vec![];
        unsafe {
            let mut context = wireless_scan_head::default();
            iw_bail!(iw_scan(
                self.skfd,
                ifname.as_ptr() as _,
                self.we_version,
                &mut context
            ));
            let mut next = context.result;

            while !next.is_null() {
                let next_ref = &*next;

                if next_ref.b.has_freq > 0 {
                    let freq = next_ref.b.freq;
                    let mut rssi = -128;

                    if next_ref.has_stats > 0 {
                        let l = next_ref.stats.qual.level as i8;
                        rssi = l as i32;
                    }

                    let ssid = CStr::from_ptr(next_ref.b.essid.as_ptr());

                    out.push(WifiInfo {
                        ssid: ssid.to_string_lossy().to_string(),
                        freq,
                        rssi,
                    });
                }
                // free(next as _);
                next = next_ref.next;
            }
        }

        Ok(out)
    }

    pub fn get_range(&self, ifname: &CString) -> Result<IWRange> {
        unsafe {
            let mut range = iw_range::default();
            iw_get_range_info(self.skfd, ifname.as_ptr(), &mut range);
            Ok(IWRange(range))
        }
    }

    pub fn freq_to_channel(&self, freq: f64, range: &IWRange) -> Result<i32> {
        unsafe {
            let channel = iw_bail!(iw_freq_to_channel(freq, &range.0));
            Ok(channel)
        }
    }

    pub fn channel_to_freq(&self, channel: i32, range: &IWRange) -> Result<f64> {
        unsafe {
            let mut freq = 0.0;
            iw_bail!(iw_channel_to_freq(channel, &mut freq, &range.0));
            Ok(freq)
        }
    }
}

impl Drop for IW {
    fn drop(&mut self) {
        unsafe {
            close(self.skfd);
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn print_version() {
        let iw = IW::new().unwrap();
        iw.print_version_info().unwrap();
    }
    #[test]
    fn dev_list() {
        let iw = IW::new().unwrap();
        let devices = iw.enum_devices(false);
        for one in devices {
            println!("{}", one.to_str().unwrap());
        }
    }
}
